package com.metel.mservice.common.jaxrs.interceptor;

import java.lang.reflect.Method;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;

import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageContentsList;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.transport.http.AbstractHTTPDestination;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.metel.mservice.common.constants.HttpStatus;
import com.metel.mservice.common.faultcode.ChatONOpenAPIFaultCode;
import com.metel.mservice.common.faultcode.message.ErrorMessage;
import com.metel.mservice.common.faultcode.message.FaultCodeResource;
import com.metel.mservice.common.jaxrs.BindErrorFaultCode;

/**
 * The JAXRSFaultInterceptor class handle JAXRS Exception.<br>
 * If failed to parse payload, put error message to Message.<br>
 * <br>
 * ** This is Workaround..!! **
 * 
 * @author sr80.choi
 */
public class JAXRSFaultInterceptor extends AbstractPhaseInterceptor<Message> {

	private final static Logger logger = LoggerFactory.getLogger(JAXRSFaultInterceptor.class);
	
	public JAXRSFaultInterceptor() {
		super(Phase.MARSHAL);
		addBefore(JAXRSFaultInterceptor.class.getName());
	}

	@Override
	public void handleMessage(Message message) throws Fault {
		processResponse(message);
	}

	private void processResponse(Message message) {
		if (isResponseAlreadyHandled(message)) {
			return;
		}

		MessageContentsList objs = MessageContentsList.getContentsList(message);
		if (objs == null || objs.size() == 0) {
			return;
		}

		Object responseObj = objs.get(0);

		Response response = null;
		if (responseObj instanceof Response) {
			Response newResponse = null;
			
			response = (Response) responseObj;

			HttpStatus currentStatus = HttpStatus.fromResponse(response);
			
			if ( currentStatus != null ) {
				switch (currentStatus) {
					case BAD_REQUEST:
					case INTERNAL_SERVER_ERROR:
						// change JAXB exception to Server error
						newResponse = createResponse(message);
						logger.info("Change JAXB exception to Server error");
		
						break;
					case Method_Not_Allowed:
						// change '405 Method Not Allowed' error to '404 Not Found' error
					case UNSUPPORTED_MEDIA_TYPE:
						// change '415 Unsupported Media Type' error to '404 Not Found' error
					case NOT_FOUND:
						// change fault message
						newResponse = createPageNotFoundResponese();
		
						if (currentStatus != HttpStatus.NOT_FOUND) {
							logger.info("Change '" + currentStatus.getStatusCode() + ", " + currentStatus.toString()
									+ "' error to '404 Not Found' error");
						} else {
							logger.info("Change fault message");
						}
		
						break;
				}
			}
			else {
				logger.debug("[JAXRSFaultInterceptor][processResponse] HttpStatus code doesn't exist.");
				return;
			}
			
			if(newResponse != null) {
				message.getExchange().put(JAXRSUtils.IGNORE_MESSAGE_WRITERS, "true");
				objs.set(0, newResponse);
			}
		}
	}

	private Response createPageNotFoundResponese() {
		ResponseBuilder error = Response.status(HttpStatus.NOT_FOUND.getStatusCode());
		
		ErrorMessage errorMessage = ErrorMessage.fromFaultCode(ChatONOpenAPIFaultCode.PAGE_NOT_FOUND);
		error.entity(errorMessage.getMessage());
		
		Response build = error.build();

		return build;
	}
	
	private Response createResponse(Message message) {
		ResponseBuilder error = Response.status(HttpStatus.BAD_REQUEST.getStatusCode());
		
		// set custom error message..
		String errorMessage = findErrorMessage(message);
		error.entity(errorMessage);

		Response build = error.build();

		return build;
	}

	private String findErrorMessage(Message message) {
		String errorMessage = null;

		Object methodInstance = findMethodInstace(message);
		if (methodInstance != null && methodInstance instanceof Method) {
			Method method = (Method) methodInstance;

			Object contentType = message.getExchange().getInMessage().get("Content-Type");
			BindErrorFaultCode annotation = method.getAnnotation(com.metel.mservice.common.jaxrs.BindErrorFaultCode.class);

			String strArg = contentType == null ? null : contentType.toString();
			errorMessage = FaultCodeResource.fromJAXRSAnnotation(annotation, strArg);
		}

		return errorMessage;
	}
	
	private Object findMethodInstace(Message message) {
		Object methodInstance = null;
		
		Object obj = message.get("org.apache.cxf.resource.method");
		if (obj != null) {
			return obj;
		}

		Exchange exchange = message.getExchange();
		if (exchange != null) {
			Message currentMessage = exchange.getInMessage();
			if (currentMessage != null) {
				methodInstance = findMethodInstace(currentMessage);
			}
		}

		return methodInstance;
	}

	private boolean isResponseAlreadyHandled(Message m) {
		return isResponseAlreadyCommited(m) || isResponseRedirected(m);
	}

	private boolean isResponseAlreadyCommited(Message m) {
		return Boolean.TRUE.equals(m.getExchange().get(AbstractHTTPDestination.RESPONSE_COMMITED));
	}

	private boolean isResponseRedirected(Message outMessage) {
		return Boolean.TRUE.equals(outMessage.get(AbstractHTTPDestination.REQUEST_REDIRECTED));
	}
}
